package cn.uidcc.fast.jackson;

import cn.uidcc.fast.jackson.util.DateUtils;
import cn.uidcc.fast.jackson.util.JSONDateFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

public class JSON {
    // static method faster
    private static ObjectMapper mapper;

    static {
        mapper = new ObjectMapper();
        // pass unknown properties
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // allow key no ""
        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        // allow key use ''
        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        // allow int startWith 0
        mapper.configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
        // allow string exist \r \n \t
        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        // after serialization json properties exclude null
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // use lower camel case
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
        // use bean names
        mapper.enable(MapperFeature.USE_STD_BEAN_NAMING);
        // dateFormat problems
        JSONDateFormat smt = new JSONDateFormat();
        mapper.setDateFormat(smt);
        mapper.setTimeZone(DateUtils.DEFAULT_TIMEZONE);
    }

    public static String toJSONString(Object o) {
        return o != null ? toJSONString(o, false) : "";
    }

    public static String toJSONString(Object o, boolean format) {
        try {
            if (o == null) {
                return "";
            }

            if (o instanceof Number) {
                return o.toString();
            }

            if (format) {
                return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);
            }
            return mapper.writeValueAsString(o);
        } catch (Exception e) {
            return "";
        }
    }

    public String toString() {
        return toJSONString();
    }

    public String toJSONString() {
        return toJSONString(this, false);
    }

    public static Object toJSON(Object javaObject) {
        if (javaObject == null) {
            return null;
        }

        if (javaObject instanceof JSON) {
            return javaObject;
        }

        if (javaObject instanceof Map) {
            Map<Object, Object> map = (Map<Object, Object>) javaObject;

            int size = map.size();

            Map innerMap;
            if (map instanceof LinkedHashMap) {
                innerMap = new LinkedHashMap((size));
            } else if (map instanceof TreeMap) {
                innerMap = new TreeMap();
            } else {
                innerMap = new HashMap(size);
            }

            JSONObject json = new JSONObject(innerMap);

            for (Map.Entry<Object, Object> entry : map.entrySet()) {
                Object key = entry.getKey();
                if (key != null) {
                    Object jsonValue = toJSON(entry.getValue());
                    json.put(key.toString(), jsonValue);
                } else {
                    json.put(key.toString(), null);
                }
            }
            return json;
        }

        if (javaObject instanceof Collection) {
            Collection<Object> collection = (Collection<Object>) javaObject;
            JSONArray array = new JSONArray(collection.size());

            for (Object item : collection) {
                Object jsonValue = toJSON(item);
                array.add(jsonValue);
            }
            return array;
        }

        Class<?> clazz = javaObject.getClass();

        if (clazz.isEnum()) {
            return ((Enum<?>) javaObject).name();
        }

        if (clazz.isArray()) {
            int len = Array.getLength(javaObject);

            JSONArray array = new JSONArray(len);

            for (int i = 0; i < len; ++i) {
                Object item = Array.get(javaObject, i);
                Object jsonValue = toJSON(item);
                array.add(jsonValue);
            }

            return array;
        }

        if (clazz.isPrimitive()
                || clazz == String.class
                || clazz == Boolean.class
                || clazz == Character.class
                || clazz == Byte.class
                || clazz == Short.class
                || clazz == Integer.class
                || clazz == BigInteger.class
                || clazz == Long.class
                || clazz == Float.class
                || clazz == Double.class
                || clazz == BigDecimal.class
                || clazz == Date.class
                || clazz == java.sql.Date.class
                || clazz == java.sql.Time.class
                || clazz == java.sql.Timestamp.class) {
            return javaObject;
        }

        String text = toJSONString(javaObject);
        return parse(text);
    }

    public static Object parse(String text) {
        if (StringUtils.startsWith(text, "{") && StringUtils.endsWith(text, "}")) {
            return parseObject(text);
        } else if (StringUtils.startsWith(text, "[") && StringUtils.endsWith(text, "]")) {
            return parseArray(text);
        } else {
            return text;
        }
    }

    public static <T> T toJavaObject(String text, Class<T> clazz) {
        try {
            if (StringUtils.isBlank(text)) {
                return null;
            }
            return mapper.readValue(text, clazz);
        } catch (Throwable e) {
            return null;
        }
    }

    public static <T> T toJavaObject(Object json, Class<T> clazz) {
        Object javaObject = toJSON(json);
        if (javaObject instanceof JSONObject) {
            return ((JSONObject) javaObject).toJavaObject(clazz);
        }
        return null;
    }

    public static <T> List<T> toJavaList(Object json, Class<T> clazz) {
        Object javaObject = toJSON(json);
        if (javaObject instanceof JSONArray) {
            ((JSONArray) javaObject).toJavaList(clazz);
        }
        return null;
    }

    public <T> T toJavaObject(TypeReference<T> tTypeReference) {
        return parseObject(toJSONString(this), tTypeReference);
    }

    public static JSONObject parseObject(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return toJavaObject(text, JSONObject.class);
        } catch (Throwable e) {
            return null;
        }
    }

    public static <T> T parseObject(String text, Class<T> clazz) {
        return toJavaObject(text, clazz);
    }

    public static <T> T parseObject(String text, TypeReference<T> tTypeReference) {
        try {
            if (StringUtils.isBlank(text)) {
                return null;
            }
            return mapper.readValue(text, tTypeReference);
        } catch (Throwable e) {
            return null;
        }
    }

    public static JSONArray parseArray(String text) {
        try {
            if (StringUtils.isBlank(text)) {
                return null;
            }
            return new JSONArray(toJavaObject(text, List.class));
        } catch (Throwable e) {
            return null;
        }
    }

    public static <T> List<T> parseArray(String text, Class<T> clazz) {
        try {
            if (StringUtils.isBlank(text)) {
                return null;
            }
            JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, clazz);
            return mapper.readValue(text, javaType);
        } catch (Throwable e) {
            return null;
        }
    }
}
